﻿/*
* Activity Tracking Log v0.1.1 - http://activitytrackinglog.codeplex.com
* Activity Tracking Log for ASP.Net & ASP.Net MVC
* Copyright (c) 2011 Vadim Burac
* Licensed under the Microsoft Public License (Ms-PL)
* http://activitytrackinglog.codeplex.com/license
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ActivityTrackingLog.Utils;
using ActivityTrackingLog.Configuration;
using ActivityTrackingLog.Data;
using System.Reflection;
using System.Linq.Expressions;
using System.Linq.Dynamic;
using System.Web.UI.WebControls;
using ActivityTrackingLog.Web.Utils;


namespace ActivityTrackingLog.Storage
{
    internal abstract class BaseRepository : ILogRepository
    {
        #region private data

        private static SortedDictionary<string, Guid> _userAgentMapping = null;
        private static object _syncObj = new object();

        #endregion

        #region ctors

        public BaseRepository()
        {
            InitializeUserAgentMapping();
        }

        #endregion

        #region virtual methods

        protected virtual BaseContext GetContext()
        {
            return new BaseContext();
        }

        #endregion

        #region ILogStorage implementation

        public void Save(ActivityItem userActivity)
        {
            using (var command = GetContext())
            {
                // remove outdated items
                if (Config.Storage.OldestDate.HasValue)
                    command.RemoveAll(item => item.TimeUtc < Config.Storage.OldestDate.Value);

                if (!Config.Storage.RecordsLimit.HasValue || command.Count() < Config.Storage.RecordsLimit.Value)
                {
                    // validate user ajent
                    if (null != userActivity.UserAgentItem)
                    {
                        lock (_syncObj)
                        {
                            if (_userAgentMapping.ContainsKey(userActivity.UserAgentItem.FullHash))
                            {
                                // replace user agent item
                                var existingId = _userAgentMapping[userActivity.UserAgentItem.FullHash];
                                userActivity.UserAgentItem.UserAgentId = existingId;
                                userActivity.UserAgentItem.IsNew = false;
                            }
                            else
                            {
                                userActivity.UserAgentItem.IsNew = true;
                                _userAgentMapping.Add(userActivity.UserAgentItem.FullHash, userActivity.UserAgentItem.UserAgentId);
                            }
                        }
                    }

                    command.Add(userActivity);
                }

                command.SubmitChanges();
            }
        }

        /*public UserActivitiesListContainer GetActivities(string userKey, int pageIndex, int pageSize)
        {
            using (var command = GetContext())
            {
                var workingList = command.GetActivityItemsQuery();
                int count = workingList.Count();
                var list = GetPageQuery(
                        GetFilteredQuery(userKey, workingList),
                        pageIndex, pageSize)
                   .ToList();

                return new UserActivitiesListContainer
                {
                    TotalRecords = count,
                    List = list
                };
            }
        }*/

        public string[][] GetFlatList<T>(string userKey, int pageIndex, int pageSize, ColumnData[] cols, out int totalRecords) where T : class
        {
            using (var command = GetContext())
            {
                if (pageIndex < 0) pageIndex = 0;
                if (pageSize < 0) pageSize = 0;

                IQueryable<T> workingList = command.GetQuery<T>();
                workingList = GetFilteredQuery<T>(workingList, userKey);
                totalRecords = workingList.Count();

                var list = GetPageQuery<T>(workingList, cols, pageIndex, pageSize)
                    .ToList();

                var plainList = list
                    .Select(item => cols.Select(colData =>
                    {
                        var prop = colData.PropertyInfo;
                        if (null != prop)
                        {
                            var value = prop.GetValue(item, null);
                            if (null != value)
                                return value.ToString();
                        }
                        return string.Empty;
                    }).ToArray())
                    .ToArray();

                return plainList;
            }
        }

        public string[][] GetGroupedFlatList<T>(string userKey, int pageIndex, int pageSize, ColumnData[] cols, out int totalRecords) where T : class
        {
            using (var command = GetContext())
            {
                if (pageIndex < 0) pageIndex = 0;
                if (pageSize < 0) pageSize = 0;
                // count column index
                int countColumnIdx = -1;
                for (int i = 0; i < cols.Length; i++)
                    if (cols[i].Name.CompareIgnoreCase("Count"))
                    {
                        countColumnIdx = i;
                        break;
                    }

                IQueryable<T> workingList = command.GetQuery<T>();
                workingList = GetFilteredQuery<T>(workingList, userKey);

                // group data
                List<GroupedDataItem> plainList = new List<GroupedDataItem>();
                string[] groupedColNames = cols.Where(c => null != c.PropertyInfo).Select(c => c.Name).ToArray();
                if (groupedColNames.Length > 0)
                {
                    var groupedList = workingList.GroupByMany(groupedColNames);
                    var groupedResultSet = groupedList.ToList();

                    // convert grouping tree to plain list
                    foreach (var groupData in groupedResultSet)
                    {
                        plainList.AddRange(GroupedDataItem.GroupToFlatList(groupData, countColumnIdx));
                    }
                }
                totalRecords = plainList.Count();

                // sorting & pagings
                Dictionary<int, Enums.SortDirectionEnum> fieldsSorting = new Dictionary<int, Enums.SortDirectionEnum>() { };
                // fill sorted cols container
                if (null != cols && cols.Length > 0)
                {
                    for (int i = 0; i < cols.Length; i++)
                        if (cols[i].SortDirection != Enums.SortDirectionEnum.None)
                        {
                            fieldsSorting.Add(i, cols[i].SortDirection);
                        }
                };
                plainList.Sort(GroupedDataItem.GetComparison(fieldsSorting));

                // paging
                var pagedPlainList = plainList
                    .Skip(pageIndex * pageSize)
                    .Take(pageSize)
                    .Select(
                    item => item.GetFlatDataArray().Select(d => null != d? d.ToString(): string.Empty).ToArray()
                    ).ToArray();

                return pagedPlainList;
            }
        }

        #endregion

        #region helpers

        private void InitializeUserAgentMapping()
        {
            lock (_syncObj)
            {
                if (null == _userAgentMapping)
                {
                    _userAgentMapping = new SortedDictionary<string, Guid>();
                    using (var command = GetContext())
                    {
                        command.GetQuery<UserAgentItem>().ToList()
                            .ForEach(item =>
                            {
                                if (!_userAgentMapping.ContainsKey(item.FullHash))
                                    _userAgentMapping.Add(item.FullHash, item.UserAgentId);
                            });
                    }
                }
            }
        }

        private static IQueryable<T> GetFilteredQuery<T>(IQueryable<T> workingList, string userKey)
        {
            // apply application flter - required for every table
            var query = workingList.Where("ApplicationKey = @0", Config.Log.ApplicationKey);
            //.Where(item => string.IsNullOrEmpty(userKey) || item.UserKey == userKey);
            if (!string.IsNullOrEmpty(userKey))
                query = query.Where("UserKey = @0", userKey);
            return query;
        }

        private static IQueryable<T> GetPageQuery<T>(IQueryable<T> workingList, ColumnData[] cols, int pageIndex, int pageSize)
        {
            string sortExpr = GetSortExpression(cols);

            // apply filter and return requested page
            if (!string.IsNullOrWhiteSpace(sortExpr))
                workingList = workingList.OrderBy(sortExpr);
            return workingList
                .Skip(pageIndex * pageSize)
                .Take(pageSize);
        }

        private static string GetSortExpression(ColumnData[] cols)
        {
            string orderExpr = "";
            if (null != cols)
            {
                var ascCols = cols.Where(c => c.SortDirection == Enums.SortDirectionEnum.Ascending).Select(c => c.Name + " ASC");
                if (ascCols.Count() > 0)
                    orderExpr += ascCols.Aggregate((c, n) => c + "," + n);

                var descCols = cols.Where(c => c.SortDirection == Enums.SortDirectionEnum.Descending).Select(c => c.Name + " DESC");
                if (descCols.Count() > 0)
                {
                    if (orderExpr.Length > 0)
                        orderExpr += ", ";
                    orderExpr += descCols.Aggregate((c, n) => c + "," + n);
                }
            }
            return orderExpr;
        }

        #endregion
    }
}
